package client

import (
	"fmt"
	"time"

	"starsine.cn/appTpl/cmn"
	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/cmn/pubsub"
	"starsine.cn/appTpl/cmn/timermgr"
	"starsine.cn/appTpl/svccenter/registry/grpc/scregistryproto"
	"starsine.cn/appTpl/svcregistry"
	registryclient "starsine.cn/appTpl/svcregistry/grpc/client"
	"starsine.cn/appTpl/svcregistry/grpc/svcregistryproto"
)

type STATE int

const (
	UNHEALTHY_CHECK STATE = iota
	HEALTHY_CHECK
)

func (t STATE) String() string {
	switch t {
	case UNHEALTHY_CHECK:
		return "un-healthy-check"
	case HEALTHY_CHECK:
		return "healthy-check"
	}
	return "unknown-state"
}

type HealthMnt struct {
	Health
	failCnt, maxCnt int
}

func (t HealthMnt) String() (str string) {
	return fmt.Sprintf("health: %s, failCnt: %d, maxCnt: %d", t.Health, t.failCnt, t.maxCnt)
}

type SvcDepStatus struct {
	svcDeps   *SvcDeps
	healthMnt [SVC_DEPEND_NUM]HealthMnt

	state     STATE
	smSwitch  chan STATE
	periodTID int64
	tm        *timermgr.TimerMgr
	appCtxt   *AppContext
}

func (t SvcDepStatus) String() (str string) {
	str += fmt.Sprintln("SvcDepStatus:")
	str += fmt.Sprintln("svcDeps: ", t.svcDeps)
	str += fmt.Sprintln("healthMnt:")
	for i, v := range t.healthMnt {
		str += fmt.Sprintf("%s: %s\n", svcDepName[i], v)
	}

	return str
}

func CreateSvcDepStatus(appCtxt *AppContext) *SvcDepStatus {
	p := new(SvcDepStatus)

	p.svcDeps = CreateSvcDeps(appCtxt)

	for i := 0; i < SVC_DEPEND_NUM; i++ {
		p.healthMnt[i] = HealthMnt{
			Health:  HealthGood,
			failCnt: 0,
			maxCnt:  3,
		}
	}

	p.state = UNHEALTHY_CHECK
	p.smSwitch = make(chan STATE)
	p.periodTID = -1
	p.tm = cmn.GetTm()
	p.appCtxt = appCtxt
	return p
}

func (p *SvcDepStatus) Start() {
	p.svcDeps.SvcDepInitGet()

	cmn.GetEvntEmiter().On("svc-register", func(params ...interface{}) {
		in := params[0].(*svcregistryproto.PubSvcRegisterReq)
		pm := pubsub.TopicParse(in.Topic)
		logs.Debugf("on svc-register, get params: %+v", pm)
		if svcDepName, ok := pm["svc-type"]; ok {
			svcDepType := GetSvcDepType(svcDepName)
			if svcDepType != -1 {
				if !p.svcDeps.HealthCheck(svcDepType) {
					err := p.svcDeps.SvcDepGet(svcDepType)
					if err != nil {
						logs.Warnf("SvcDepStatus on svc-register to SvcDepGet fail, err: %s", err)
					}
				}
			}
		}
	})

	go p.sm()
	p.smSwitch <- UNHEALTHY_CHECK
}

func (p *SvcDepStatus) sm() {
	logs.Infof("SvcDepStatus sm routine start")
	p.appCtxt.Wg.Add(1)
	defer func() {
		logs.Warnf("SvcDepStatus sm routine exit")
		p.appCtxt.Wg.Done()
	}()

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case s := <-p.smSwitch:
			logs.Debugf("switch to %s", s)
			switch s {
			case UNHEALTHY_CHECK:
				ticker.Reset(1 * time.Second)
			case HEALTHY_CHECK:
				ticker.Reset(15 * time.Second)
			}
			p.state = s

		case <-ticker.C:
			p.healthCheck()

		case <-p.appCtxt.Ctx.Done():
			logs.Warnf("SvcDepStatus sm routine ctxt done to quit")
			return
		}
	}
}

func (p *SvcDepStatus) switchTo(s STATE) {
	if p.state != s {
		go func() {
			p.smSwitch <- s
		}()
	}
}

func (p *SvcDepStatus) healthCheck() {
	flaw := false
	for i := SVC_DEPEND_DEMO; i < SVC_DEPEND_NUM; i++ {
		if !p.svcDeps.HealthCheck(i) {
			p.healthMnt[i].failCnt++
			if p.healthMnt[i].failCnt >= p.healthMnt[i].maxCnt {
				p.healthMnt[i].Health = HealthDEAD
				logs.Warnf("svc dep(%s) health set to %s", svcDepName[i], p.healthMnt[i].Health)

				p.svcDeps.ResetHandler(i)
				err := p.svcDeps.SvcDepGet(i)
				if err != nil {
					logs.Warnf("SvcDepStatus.SvcDepGet(%s) fail, err: %s", svcDepName[i], err)
					flaw = true
				}
			} else {
				p.healthMnt[i].Health = HealthBAD
				logs.Warnf("svc dep(%s) health set to %s", svcDepName[i], p.healthMnt[i].Health)

				registryclient.SubSvcRegister(&scregistryproto.SubSvcRegisterReq{
					InstanceID: svcregistry.GetInstanceID(),
					Topic:      fmt.Sprintf("svc-register?user-space=%s;svc-type=%s", p.svcDeps.userSpace, p.svcDeps.data[i].svcType),
					IsAlways:   false,
				})
				logs.Warnf("health check fail to subsvcupdate(%s)", svcDepName[i])
			}
		} else {
			p.healthMnt[i].failCnt = 0
			p.healthMnt[i].Health = HealthGood
		}
	}

	if flaw {
		p.switchTo(UNHEALTHY_CHECK)
	} else {
		p.switchTo(HEALTHY_CHECK)
	}
}
